package com.proyecto.track.service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.transaction.annotation.Transactional;

import com.proyecto.track.action.ProyectoAction;
import com.proyecto.track.action.dto.ProyectoDTO;
import com.proyecto.track.action.dto.TareaDTO;
import com.proyecto.track.action.dto.UsuarioDTO;
import com.proyecto.track.bo.*;
import com.proyecto.track.constantes.ConsEstadoDeProyecto;
import com.proyecto.track.constantes.ConsEstadoDeTarea;
import com.proyecto.track.dao.*;
import com.proyecto.track.utils.MD5Hash;
import com.proyecto.track.utils.NoHayHorasDisponiblesException;

/**
 * El servicio que ofrece todo el acceso a datos.
 */
public class ProyectoService implements IProyectoService {

	private Logger log = Logger.getLogger(ProyectoAction.class);

	/** The proyecto dao. */
	private ProyectoDAO proyectoDAO;

	/** The estado proyecto dao. */
	private EstadoProyectoDAO estadoProyectoDAO;

	/** The estado tarea dao. */
	private EstadoTareaDAO estadoTareaDAO;

	/** The usuario dao. */
	private UsuarioDAO usuarioDAO;

	/** The tarea dao. */
	private TareaDAO tareaDAO;

	/** The tipo tarea dao. */
	private TipoTareaDAO tipoTareaDAO;

	/** The comentario dao. */
	private ComentarioDAO comentarioDAO;
	
	/** The roles dao. */
	private RolesDAO rolesDAO;

	/**
	 *	Actualiza los datos del usuario.
	 *
	 *	@param El usuario que se quiere actualizar.
	 */
	@Transactional
	public void actualizarUsuario(Usuario usuario) {
		MD5Hash md5 = new MD5Hash();
		String pass = md5.hashearPass(usuario.getClave());
		usuario.setClave(pass);
		usuarioDAO.actualizarUsuario(usuario);
		
	}
	
	/**
	 *	Borra el usuario con el id del par�metro.
	 *
	 * 	@param el id del usuario que se quiere borrar
	 */
	@Transactional
	public void borrarUsuario(Long idUsuario) {
		usuarioDAO.borrarUsuario(idUsuario);
		
	}

	/**
	 * Por defecto, cuando se crea un usuario se le setea el campo
	 * habilitado en true y le dice al DAO de usuarios que cree un nuevo usuario.
	 * 
	 * @param El usuario que se quiere crear
	 * @return Long el id del nuevo usuario
	 * 	
	 */
	@Transactional
	public Long crearUsuario(Usuario usuario) {
		usuario.setHabilitado(true);
		MD5Hash md5 = new MD5Hash();
		String pass = md5.hashearPass(usuario.getClave());
		usuario.setClave(pass);
		return usuarioDAO.guardarUsuario(usuario);
		
	}
	
	/**
	 * Devuelve el usuario con el id que se recibe por par�metro
	 * 
	 * @param El id del usuario que se quiere recuperar
	 * @return Long el id del nuevo usuario
	 * 	
	 */
	@Transactional
	public Usuario recuperarUsuario(Long idUsuario) {
		return usuarioDAO.getUsuarioById(idUsuario);
	}

	/**
	 * Devuelve toda la lista de usuarios
	 * 
	 * @return Una List de usuarios.
	 */
	@Transactional
	public List<Usuario> getAllUsuarios() {
		return usuarioDAO.getAllUsuarios();
	}
	
	/**
	 * Devuelve el usuario con el username del par�metro.
	 * 
	 * @param String Nombre de Usuario.
	 * @return Una List de usuarios.
	 */
	@Transactional
	public Usuario recuperarUsuarioPorUsername(String nombreUsuario) {
		return usuarioDAO.getUsuarioByUsername(nombreUsuario);
	}
	
	/**
	 * Devuelve la lista de todos los proyectos.
	 * 
	 * @return Una List de proyectos.
	 */
	@Transactional
	public List<Proyecto> listarProyectos() {
		return proyectoDAO.getAllProyectos();
	}
	
	/**
	 * Devuelve el proyecto con id pasado por par�metro.
	 * 
	 * @param Long un id de proyecto.
	 * @return Un de proyecto.
	 */
	@Transactional
	public Proyecto recuperarProyecto(Long idProyecto) {
		return proyectoDAO.getProyectoById(idProyecto);
	}
	
	/**
	 * Devuelve la lista de todos los estados de proyecto.
	 * 
	 * @return List<EstadoProyecto> la lista de Estodos de Proyecto.
	 */
	@Transactional
	public List<EstadoProyecto> listarEstadosProyecto() {
		return estadoProyectoDAO.getAllEstados();
	}
	
	/**
	 * Crea un nuevo proyecto. Por defecto, se le pone la fecha de alta
	 * (actual) y el estado "Abierto".
	 * 
	 * @param Un objeto proyecto con los datos b�sicos para crearlo.
	 * @return Long el id del nuevo proyecto.
	 */
	@Transactional
	public Long crearProyecto(Proyecto proyecto) {
		
		Date fecha = new Date();
		//Por defecto la fecha de alta es la de creaci�n-
		proyecto.setFechaDeAlta(fecha);
		//Idem la de �ltimo cambio
		proyecto.setFechaDeUtlimoCambio(fecha);
		//Por defecto, se cre con el estado Abierto
		proyecto.setEstadoProyecto(recuperarEstadoProyecto(ConsEstadoDeProyecto.abierto));
		return proyectoDAO.guardarProyecto(proyecto);
	}

	/**
	 * Devuelve el estado de proyecto con id que recibe por par�metro.
	 * 
	 * @param El id del estado proyecto que se quiere recuperar.
	 * @return EstadoProyeco el estado proyecto con id del par�metro.
	 */
	@Transactional
	public EstadoProyecto recuperarEstadoProyecto(Long idEstado) {
		return estadoProyectoDAO.getEstadoProyectoById(idEstado);
	}
	
	/**
	 * Actualiza un proyecto. Por defecto, se actualiza la fecha de
	 * Último cambio con la fecha actual.
	 * 
	 * @param Un objeto proyecto.
	 */
	@Transactional
	public void actualizarProyecto(Proyecto proyecto) {
		Proyecto proyectoViejo = recuperarProyecto(proyecto.getIdProyecto());
		Date fecha = new Date();
		//Idem la de último cambio
		proyecto.setFechaDeUtlimoCambio(fecha);
		proyecto.setFechaDeAlta(proyectoViejo.getFechaDeAlta());
		proyecto.setTareas(proyectoViejo.getTareas());
		proyecto.setRecursosAsignados(proyectoViejo.getRecursosAsignados());
		proyectoDAO.actualizarProyecto(proyecto);
	}
	
	/**
	 * Borra un proyecto.
	 * 
	 * @param El id del proyecto que se quiere borrar.
	 */
	@Transactional
	public void borrarProyecto(Long idProyecto) {
		proyectoDAO.borrarProyecto(idProyecto);
	}
	
	/**
	 * Devuelve el listado de Proyectos en DTOs.
	 * 
	 * @return List<ProyectoDTO> el listado de proyectos en DTOs
	 */
	@Transactional
	public List<ProyectoDTO> listarProyectosDTO() {
		List<Proyecto> projects = listarProyectos();
		List<ProyectoDTO> proyectos = new ArrayList<ProyectoDTO>();
		for (Proyecto proyecto : projects) {
			proyectos.add(proyecto.toDTO());
		}
		return proyectos;
	}
	
	/**
	 * Devuelve el listado de Tareas en DTOs.
	 * 
	 * @return List<TareaDTO> el listado de tareas en DTOs
	 */
	@Transactional
	public List<TareaDTO> listarTareasDTO(Long idProyecto) {
		List<TareaDTO> tareas = new ArrayList<TareaDTO>();
		for (Tarea t : recuperarProyecto(idProyecto).getTareas()) {
			tareas.add(t.toDTO());
		}
		return tareas;
	}

	/**
	 *	Busca si existe un usuario por su nombre.
	 *
	 * 	@param El String con el nombre del usuario que se quiere encontrar
	 * 
	 *  @return Un Boolean que indica si el usuario existe o no
	 */
	@Transactional
	public boolean existeUsuarioConNombre(String nombre) {
		log.info(this.getClass().getCanonicalName() + " Consultando si existe el usuario");
		if(usuarioDAO.getUsuarioByName(nombre)!=null) return true;
		return false;
	}
	
	/**
	 * Devuelve el listado de Usuarios (en DTOs) filtrado si contiene el string de búsqueda
	 * para el autocomplete
	 * 
	 * @param String nombre, algunas letras del nombre para buscar
	 * @return List<UsuarioDTO> el listado de usuarios en DTOs
	 */
	@Transactional
	public  List<UsuarioDTO> usuarioEmpiezaConDTO(String nombre) {
		log.info(this.getClass().toString() + " - buscando usuarios que empiezan con " + nombre);
		List <Usuario> usuarios  = usuarioDAO.getAllUsuarios();
		List<UsuarioDTO> responsablesDTO = new ArrayList<UsuarioDTO>();
		for (Usuario usuario : usuarios) {
			if (usuario.getNombreCompleto().toLowerCase().contains(nombre.toLowerCase())){
				responsablesDTO.add(usuario.toDTO());
			}
		}
		
		return responsablesDTO;
	}
	
	/**
	 * Devuelve el listado de roles.
	 * 
	 * @return List<Rol> el listado de Roles.
	 */
	@Transactional
	public List<Rol> getAllRoles() {
		
		return rolesDAO.getAllRoles();
	}
	
	/**
	 * Devuelve el Objeto Rol con id pasado por parámetro.
	 * 
	 * @param Long el id del Rol.
	 * @return Rol el objeto Rol con id de parámetro.
	 */
	@Transactional
	public Rol recuperarRol(Long idRol) {
		return rolesDAO.getRolById(idRol);
	}
	
	@Transactional
	public Long guardarComentario(Comentario comentario) {
		comentario.setFechaAlta(new Date());
		return comentarioDAO.guardarComentario(comentario);
	}
	
	/**
	 * Devuelve el Objeto Tarea con id pasado por parámetro.
	 * 
	 * @param Long el id de la tarea.
	 * @return Tarea el objeto Tarea con id de parámetro.
	 */
	@Transactional
	public Tarea recuperarTarea(Long idTarea){
		return tareaDAO.getTareaById(idTarea);
	}
	
	/**
	 * Devuelve el listado de Estados Tarea.
	 * 
	 * @return List<EstadoTarea> el listado de Estados Tarea.
	 */
	@Transactional
	public List<EstadoTarea> getAllEstadosTarea(){
		return estadoTareaDAO.getAllEstadoTareas();
	}
	
	/**
	 * Devuelve el listado de Tipos Tarea.
	 * 
	 * @return List<TipoTarea> el listado de Tipo Tarea.
	 */
	@Transactional
	public List<TipoTarea> getAllTiposTarea(){
		return tipoTareaDAO.getAllTipoTareas();
	}
	
	/**
	 * Devuelve el listado de Tipos Tarea.
	 * 
	 * @return List<TipoTarea> el listado de Tipo Tarea.
	 */
	@Transactional
	public Long crearTarea(Tarea tarea) throws NoHayHorasDisponiblesException {
		tarea.setEstadoTarea(estadoTareaDAO.getEstadoTareaById(ConsEstadoDeTarea.nueva));
        if (tarea.getHorasAsignadas() <= tarea.getProyecto().recuperarhorasRestantesProyecto()){
                return tareaDAO.guardarTarea(tarea);
        }else{
                throw new NoHayHorasDisponiblesException("No se puede cargar la tarea porque requiere más horas de las que el proyecto" +
                                "tiene disponible.");
        }
	}
	
	/**
	 * Devuelve el Objeto Tipo Tarea con id pasado por parámetro.
	 * 
	 * @param Long el id del Tipo de tarea.
	 * @return TipoTarea el objeto Tipo Tarea con id de parámetro.
	 */
	@Transactional
	public TipoTarea recuperarTipoTarea(Long idTipoTarea){
		return tipoTareaDAO.getTipoTareaById(idTipoTarea);
	}
	
	/**
	 * Actualiza una tarea, chequeando que haya horas disponibles para
	 * la modificación.
	 * 
	 * @param Un objeto Tarea.
	 */
	@Transactional
    public void actualizarTarea(Tarea tarea) throws NoHayHorasDisponiblesException{
		
		Tarea tareaVieja = recuperarTarea(tarea.getIdTarea());
		tarea.setAsignados(tareaVieja.getAsignados());
		tarea.setComentarios(tareaVieja.getComentarios());
		
		if (tarea.getHorasAsignadas() <= tarea.getProyecto().recuperarhorasRestantesProyecto()){
			tareaDAO.actualizarTarea(tarea);
		}else{
			throw new NoHayHorasDisponiblesException("No se puede cargar la tarea porque requiere más horas de las que el proyecto" +
                                    "tiene disponible.");
		}

    }
	
	/**
	 * Borra una tarea.
	 * 
	 * @param Long El id de la Tarea que se quiere borrar.
	 */
	@Transactional
	public void borrarTarea(Long idTarea){
		tareaDAO.borrarTarea(idTarea);
	}
	
	public ProyectoDAO getProyectoDAO() {
		return proyectoDAO;
	}

	public void setProyectoDAO(ProyectoDAO proyectoDAO) {
		this.proyectoDAO = proyectoDAO;
	}

	public EstadoProyectoDAO getEstadoProyectoDAO() {
		return estadoProyectoDAO;
	}

	public void setEstadoProyectoDAO(EstadoProyectoDAO estadoProyectoDAO) {
		this.estadoProyectoDAO = estadoProyectoDAO;
	}

	public EstadoTareaDAO getEstadoTareaDAO() {
		return estadoTareaDAO;
	}

	public void setEstadoTareaDAO(EstadoTareaDAO estadoTareaDAO) {
		this.estadoTareaDAO = estadoTareaDAO;
	}

	public UsuarioDAO getUsuarioDAO() {
		return usuarioDAO;
	}

	public void setUsuarioDAO(UsuarioDAO usuarioDAO) {
		this.usuarioDAO = usuarioDAO;
	}

	public TareaDAO getTareaDAO() {
		return tareaDAO;
	}

	public void setTareaDAO(TareaDAO tareaDAO) {
		this.tareaDAO = tareaDAO;
	}

	public TipoTareaDAO getTipoTareaDAO() {
		return tipoTareaDAO;
	}

	public void setTipoTareaDAO(TipoTareaDAO tipoTareaDAO) {
		this.tipoTareaDAO = tipoTareaDAO;
	}

	public ComentarioDAO getComentarioDAO() {
		return comentarioDAO;
	}

	public void setComentarioDAO(ComentarioDAO comentarioDAO) {
		this.comentarioDAO = comentarioDAO;
	}

	public RolesDAO getRolesDAO() {
		return rolesDAO;
	}

	public void setRolesDAO(RolesDAO rolesDAO) {
		this.rolesDAO = rolesDAO;
	}
}